View Javadoc
1 /* FOREGEJ - FOrmatting REfactoring GEnerating Java 2 * 3 * Copyright (C) 2003 Andreas Arrgard 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Lesser General Public 7 * License as published by the Free Software Foundation; either 8 * version 2.1 of the License, or (at your option) any later version. 9 * 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Lesser General Public License for more details. 14 * 15 * You should have received a copy of the GNU Lesser General Public 16 * License along with this library; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 */ 19 package com.octagroup.foregej.antlr; 20 import java.util.HashSet; 21 import java.util.Vector; 22 import com.octagroup.foregej.Settings; 23 import com.octagroup.foregej.java.lang.ast.AST_IF; 24 import com.octagroup.foregej.java.lang.ast.AST_SLIST; 25 import antlr.Token; 26 import antlr.collections.AST; 27 /*** 28 * Class used to write output from nodes. 29 */ 30 public class NodeWriter 31 { 32 /*** 33 * Integer used to signal that we cannot wrap on a char or string. 34 */ 35 public static final int WRAP_NOT=0; 36 /*** 37 * Integer used to signal that we can wrap before a char or string. 38 */ 39 public static final int WRAP_BEFORE=1; 40 /*** 41 * Integer used to signal that we can wrap after a char or string. 42 */ 43 public static final int WRAP_AFTER=2; 44 /*** 45 * Integer used to signal that we can wrap on a char or string. 46 */ 47 public static final int WRAP_ON=3; 48 /*** 49 * This string buffer contains th written output 50 */ 51 private StringBuffer sb_=new StringBuffer(); 52 /*** 53 * A vector that contains a set of indentations 54 */ 55 private Vector indentations_=new Vector(); 56 /*** 57 * the parent node 58 */ 59 protected Vector parents_=new Vector(); 60 /*** 61 * a hashmap of all the written tokens before and after a node. 62 */ 63 protected HashSet writtenTokens_=new HashSet(); 64 /*** 65 * A reference to the indentation at the latest wrap prospect. 66 */ 67 protected String latestWrapProspectIndent_=null; 68 /*** 69 * Signals how the wrapping choulb be performed. ie should the 70 * character be removed, or should wrapping occurr before or after. 71 */ 72 protected int latestWrapProspectType_=0; 73 /*** 74 * The position of the latest line wrap prospect. If this is the same 75 * as the lastLineWrap we have not discovered any characters that we 76 * can wrap at. 77 */ 78 protected int latestWrapProspectPos_=0; 79 /*** 80 * The position of the last line wrap. 81 */ 82 protected int lastLineWrapPos_=0; 83 /*** 84 * How do we deal with lines that exceed the maximum number of 85 * characters. 86 */ 87 protected boolean canWrapLines_=true; 88 /*** 89 * This variable is assigned as we exit the write(AST) method and used 90 * to check if we can wrap lines between two asts as we enter it again 91 * with a new ast. 92 */ 93 protected AST lastAST_=null; 94 /*** 95 * Returns the output of the underlying buffer 96 * 97 * @return The text written to the underlying buffer. 98 */ 99 public String toString() 100 { 101 return sb_.toString(); 102 } 103 /*** 104 * Copies the state from one node writer to another. 105 * <p> 106 * This method is used when we need to change the implementation of 107 * the node writer. For example when we switch between writing java 108 * code and java comments. 109 * </p> 110 * 111 * @param nw 112 */ 113 public void copyThisStateTo(NodeWriter nw) 114 { 115 nw.sb_=sb_; 116 nw.indentations_=indentations_; 117 nw.lastLineWrapPos_=lastLineWrapPos_; 118 nw.latestWrapProspectPos_=latestWrapProspectPos_; 119 nw.latestWrapProspectType_=latestWrapProspectType_; 120 nw.latestWrapProspectIndent_=latestWrapProspectIndent_; 121 // 122 // ignore the parents until we find an "acceptable" way 123 // of initializing parent nodes... 124 // 125 //nw.parents_ = parents_; 126 } 127 /*** 128 * Writes the supplied ast to the underlying stream. 129 * 130 * @param ast the ast to write. 131 * @throws RuntimeException if ast is null 132 */ 133 public void write(AST ast) 134 { 135 if(ast==null) { 136 throw new RuntimeException("ast is null!"); 137 } 138 BaseAST bast=(BaseAST)ast; 139 // 140 // store how we deal with line wrapping 141 // 142 boolean canWrapLines=canWrapLines_; 143 if(bast.getWrappingPreference()!=BaseAST.WRAP_PREF_NONE) { 144 canWrapLines_=bast.getWrappingPreference()==BaseAST.WRAP_PREF_OK; 145 } 146 // 147 // check if we can wrap between the last ast and this one 148 // 149 if(canWrapBetween(lastAST_, ast)) { 150 latestWrapProspectIndent_=getIndentation(); 151 latestWrapProspectPos_=sb_.length(); 152 latestWrapProspectType_=WRAP_BEFORE; 153 } 154 // the parent ast stack should point to the supplied ast. 155 // this way we can reach the parent by referencing one level up. 156 truncateParentStackTo(ast); 157 pushParent(ast); 158 (// System.out.println("Writing ast:" + ast.getClass().getName()); 159 (BaseAST)ast).write(this); 160 // restore to how we dealt with wrapping before this AST was written 161 canWrapLines_=canWrapLines; 162 // this is the last ast written... 163 lastAST_=ast; 164 } 165 /*** 166 * Writes data to the underlying buffer.<br> 167 * It checks if the token has been written before and does not write 168 * it if that is the case. 169 * 170 * @param token the token to get the text from. 171 */ 172 public void write(Token token) 173 { 174 write(token, true); 175 } 176 /*** 177 * Writes data to the underlying buffer.<br> 178 * 179 * @param token the token to get the text from. 180 * @param checkWritten 181 */ 182 public void write(Token token,boolean checkWritten) 183 { 184 if(checkWritten&&isWritten(token)) { 185 return; 186 } 187 write(token.getText()); 188 markAsWritten(token); 189 } 190 /*** 191 * Writes data to the underlying buffer. 192 * 193 * @param s the string to output 194 */ 195 public void write(String s) 196 { 197 char[] carr=s.toCharArray(); 198 for(int i=0; i<s.length(); i++){ 199 char c=carr[i]; 200 switch(c) { 201 case '\r': 202 203 // if the next character is a newline we skip the newline. 204 if(carr.length>i+1&&carr[i+1]=='\n') i++; 205 case '\n': 206 207 newLine(); 208 break; 209 default: 210 211 sb_.append(c); 212 } 213 // 214 // check if the current line has exceeded the maximum 215 // number of columns. If that is the case we wrap at the latest 216 // character that allowed it... 217 // 218 if(sb_.length()-lastLineWrapPos_>=Settings.getLineLength()) { 219 wrap(); 220 } 221 // 222 // check if it is possible to wrap in this char. If that 223 // is the case store the position and whiw the wrapping 224 // should be done. 225 // 226 if(canWrapLines_&&canWrapOn(c)!=WRAP_NOT) { 227 latestWrapProspectType_=canWrapOn(c); 228 latestWrapProspectPos_=sb_.length()-1; 229 latestWrapProspectIndent_=getIndentation(); 230 } 231 } 232 } 233 /*** 234 * Returns how we can wrap on the supplied character. 235 * 236 * @param c the char to switch on 237 * @return an integer specifying which wrap algorithm to use. 238 */ 239 protected int canWrapOn(char c) 240 { 241 if(c==' ') { 242 return WRAP_ON; 243 } 244 return WRAP_NOT; 245 } 246 /*** 247 * Returns how we can wrap between the supplied asts. 248 * 249 * @param firstAst the ast to switch on 250 * @param secondAst the ast to switch on 251 * @return true if we can wrap between the asts. 252 */ 253 protected boolean canWrapBetween(AST firstAst,AST secondAst) 254 { 255 return false; 256 } 257 /*** 258 * Wraps a line at the lastLineWrapPos_ index. 259 */ 260 private void wrap() 261 { 262 if(latestWrapProspectPos_==lastLineWrapPos_) { 263 return; 264 } 265 String lineEnding=Settings.getLineEnding(); 266 if(latestWrapProspectType_==WRAP_ON) { 267 removeSpacesAfter(latestWrapProspectPos_); 268 sb_.insert(latestWrapProspectPos_, 269 lineEnding+latestWrapProspectIndent_); 270 lastLineWrapPos_=latestWrapProspectPos_+lineEnding.length(); 271 } else { 272 int wrapPos=(latestWrapProspectType_==WRAP_BEFORE)?latestWrapProspectPos_:latestWrapProspectPos_+1; 273 removeSpacesAfter(wrapPos); 274 sb_.insert(wrapPos, lineEnding+latestWrapProspectIndent_); 275 lastLineWrapPos_=wrapPos+lineEnding.length(); 276 } 277 latestWrapProspectPos_=lastLineWrapPos_; 278 } 279 /*** 280 * Helper method that removes all the spaces after the supplied 281 * position. 282 * <p> 283 * This method is used when we wrap the lines to make sure that the 284 * indentation becomes correct. 285 * </p> 286 * 287 * @param startPos 288 */ 289 public void removeSpacesAfter(int startPos) 290 { 291 int endPos=startPos; 292 while(sb_.length()>endPos&&sb_.charAt(endPos)==' ')endPos++; 293 sb_.replace(startPos, endPos, ""); 294 } 295 /*** 296 * Writes a new line sequence and then an indentation to the 297 * underlying buffer. 298 */ 299 public void newLine() 300 { 301 // 302 // create line ending 303 // 304 sb_.append(Settings.getLineEnding()); 305 lastLineWrapPos_=sb_.length(); 306 latestWrapProspectPos_=lastLineWrapPos_; 307 // 308 // and add indentation 309 // 310 sb_.append(getIndentation()); 311 } 312 /*** 313 * Indents with the number of spaces defined in the settings. 314 * <p></p> 315 */ 316 public void pushIndentation() 317 { 318 pushIndentation(Settings.getIndentation()); 319 } 320 /*** 321 * Indents with the supplied string. 322 * <p></p> 323 * 324 * @param indent 325 */ 326 public void pushIndentation(String indent) 327 { 328 StringBuffer sb=new StringBuffer(getIndentation()); 329 sb.append(indent); 330 indentations_.add(sb.toString()); 331 } 332 /*** 333 * Indents with the supplied number of spaces. 334 * <p></p> 335 * 336 * @param indent 337 */ 338 public void pushIndentation(int indent) 339 { 340 StringBuffer sb=new StringBuffer(getIndentation()); 341 for(int i=0; i<indent; i++){ 342 sb.append(' '); 343 } 344 indentations_.add(sb.toString()); 345 } 346 /*** 347 * Pushes an indentaion string with the same number of spaces as the 348 * the number of characters on the last line. 349 */ 350 public void pushIndentationCurrentPos() 351 { 352 StringBuffer indentation=new StringBuffer(); 353 for(int i=sb_.length()-lastLineWrapPos_; i>0; i--){ 354 indentation.append(' '); 355 } 356 indentations_.add(indentation.toString()); 357 } 358 /*** 359 * Helper method that scans the undelying buffer and returns the 360 * number of characters in the last line. 361 * 362 * @return the number of characters in the last line. 363 */ 364 private Integer getLinePos() 365 { 366 for(int i=sb_.length()-1; i>=0; i--){ 367 char c=sb_.charAt(i); 368 switch(c) { 369 case '\n': 370 case '\r': 371 372 return new Integer(sb_.length()-i); 373 default: 374 375 } 376 } 377 // continue 378 return new Integer(0); 379 } 380 /*** 381 * This is the same as writing <code>popIndentation()</code>and then 382 * <code>write(s)</code>.<br> 383 * It exists because the <code>JavaNodeWriter</code>should be able to 384 * write comments before popping the indentation. 385 * 386 * @param s the string to write 387 * @return the last indentation. 388 */ 389 public String popIndentationAndWrite(String s) 390 { 391 String lastIndent=popIndentation(); 392 write(s); 393 return lastIndent; 394 } 395 /*** 396 * Pops an indentation level off the stack. 397 * 398 * @return the indentation level that was removed. 399 */ 400 public String popIndentation() 401 { 402 if(indentations_.size()==0) throw new IllegalStateException("NodeWriter: called popIndentaion with no indentations on the stack"); 403 String oldIndentation=(String)indentations_.remove(indentations_.size()-1); 404 String currIndentation=getIndentation(); 405 boolean indentationOnly=sb_.toString().endsWith(oldIndentation); 406 if(indentationOnly) { 407 if(oldIndentation.startsWith(currIndentation)) { 408 int remove=oldIndentation.length()-currIndentation.length(); 409 sb_.setLength(sb_.length()-remove); 410 } else if(currIndentation.startsWith(oldIndentation)) { 411 String add=currIndentation.substring(oldIndentation.length(), 412 currIndentation.length()); 413 sb_.append(add); 414 } else { 415 throw new IllegalStateException("Indentations messed up!"); 416 } 417 } 418 return oldIndentation; 419 } 420 /*** 421 * Returns the indentation level. 422 * 423 * @return the indentation level. 424 */ 425 public String getIndentation() 426 { 427 if(indentations_.size()==0) return ""; 428 return (String)indentations_.get(indentations_.size()-1); 429 } 430 /*** 431 * Sets the parent node. 432 * 433 * @param parent 434 */ 435 private void pushParent(AST parent) 436 { 437 parents_.add(parent); 438 } 439 /*** 440 * Helper method that determines if the supplied node is a child or 441 * sibling node to the supplied parent. 442 * 443 * @param parent 444 * @param child 445 * @return 446 */ 447 private boolean isChild(AST parent,AST child) 448 { 449 AST node=parent.getFirstChild(); 450 while(node!=null){ 451 if(child==node) { 452 return true; 453 } 454 node=node.getNextSibling(); 455 } 456 return false; 457 } 458 /*** 459 * Helper method that trunkates the parent stack to the position where 460 * the supplied node is a child member. 461 * <p> 462 * If the parents sttck is empty this method dont do nothing. 463 * </p> 464 * 465 * @param child 466 */ 467 private void truncateParentStackTo(AST child) 468 { 469 // if the parents stack is empty we just return. 470 // this is a special case 471 if(parents_.size()==0) { 472 return; 473 } 474 int i=parents_.size()-1; 475 for(; i>=0; i--){ 476 if(isChild((AST)parents_.get(i), child)) { 477 break; 478 } 479 } 480 // if node is not child of any current parents 481 // then we have not written the parent using the 482 // node writer - at this point this is illegal. 483 if(i==-1) throw new IllegalStateException("Failed to find parent of node."); 484 parents_.setSize(i+1); 485 } 486 /*** 487 * @return 488 */ 489 public AST getPreviousAST() 490 { 491 return getPreviousAST((AST)parents_.get(parents_.size()-1)); 492 } 493 /*** 494 * @param ast 495 * @return 496 */ 497 public AST getPreviousAST(AST ast) 498 { 499 AST parent=((BaseAST)ast).getParentAst(); 500 if(parent==null) { 501 //throw new IllegalStateException("Failed to find parent to ast."); 502 return null; 503 } 504 AST prevNode=null; 505 AST node=parent.getFirstChild(); 506 while(node!=null){ 507 if(node==ast) { 508 return prevNode; 509 } 510 prevNode=node; 511 node=node.getNextSibling(); 512 } 513 throw new IllegalStateException("Failed to find node in parent"); 514 } 515 /*** 516 * Marks the supplied token as written. 517 * 518 * @param token the token to mark. 519 */ 520 public void markAsWritten(Token token) 521 { 522 writtenTokens_.add(token); 523 } 524 /*** 525 * Returns true if the supplied token is written. 526 * 527 * @param token the token to determine if written. 528 * @return true if the supplied token is written. 529 */ 530 public boolean isWritten(Token token) 531 { 532 return writtenTokens_.contains(token); 533 } 534 /*** 535 * Helper method that writes the supplied ast as a statement. 536 * <p> 537 * This method is invoked from the <code>if</code>and 538 * <code>while</code>asts. 539 * </p> 540 * 541 * @param ast the ast to write 542 */ 543 public void writeStatement(AST ast) 544 { 545 if(ast instanceof AST_SLIST||ast instanceof AST_IF) { 546 write(ast); 547 } else { 548 write(ast); 549 write(";"); 550 } 551 } 552 /*** 553 * Helper method that writes the supplied ast in parenthesis. 554 * <p> 555 * This method is invoked from the asts that may need this 556 * functionality to separate two operands with same precidence. 557 * </p> 558 * 559 * @param op the operator to write in the parenthesis 560 */ 561 public void writeInParenthesis(AST op) 562 { 563 write("("); 564 pushIndentationCurrentPos(); 565 write(op); 566 popIndentation(); 567 write(")"); 568 } 569 /*** 570 * Returns true if the last line is empty. 571 * <p> 572 * This method returns true if the internal string buffer ends with 573 * the new line fetched frm the settings and the current indentation. 574 * </p> 575 * 576 * @return 577 */ 578 public boolean isLastLineEmpty() 579 { 580 String comp=Settings.getLineEnding()+getIndentation(); 581 String ending=sb_.substring(sb_.length()-comp.length(), 582 sb_.length()); 583 return comp.equals(ending); 584 } 585 /*** 586 * Sets the flag that indicates if we are allowed to wrap lines. 587 * 588 * @param b should wrapping be enabled 589 * @return the old status. 590 */ 591 public boolean enableWrapping(boolean b) 592 { 593 boolean retVal=canWrapLines_; 594 canWrapLines_=b; 595 return retVal; 596 } 597 }

This page was automatically generated by Maven